import json
import os,sys,threading,time
import serial,serial.tools.list_ports
import pyqtgraph as pg
import numpy as np
import pandas as pd

from PyQt5.QtCore import Qt,QSize,pyqtSignal,QTimer,QRect,QThread
from PyQt5.QtGui import QResizeEvent,QTextCursor
from PyQt5.QtWidgets import *
from mymainwindow_ui import Ui_MainWindow
from datetime import datetime
import binascii

from myplot import MyPlot

import config_file
from textDisp import LabelDisp
from configDisp import ConfigLabelDisp

# 创建配置文件对象,使用config_g获取配置文件的内容
config_g =  config_file.ConfigFile()
config_g.loadFile(config_file.configFilePath)               # 加载当前目录中的配置文件到config_g.config
print("配置文件路径：",config_file.configFilePath)           # 打印配置文件路径
#print(json.dumps(config_g.config, indent=4, ensure_ascii=False))      # 打印配置文件内容

class MyMainWindow(QMainWindow, Ui_MainWindow):

    sig_readData = pyqtSignal(object)
    sig_updateCount = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.ui_init()
        self.var_init()
        self.serial_init()
        self.textDispInit()
        self.configTextDispInit()
        self.graphDispInit()
        self.sig_init()

    def ui_init(self):
        self.setWindowTitle("Serial Debugging Assistant (SDA)") 
        self.addStateBarView()                                      # 状态栏添加发送和接收计数
        self.txbRecv.document().setMaximumBlockCount(10000)         # 设置QTextBrowser最大10000行

        # 串口相关配置初始化
        self.Baud   = ('4800','9600','19200','38400','115200')
        self.Stop   = ('1','1.5','2')
        self.Data   = ('8','7','6','5')
        self.Check  = ('None','Odd','Even')
        self.cbxBaudRate.addItems(self.Baud)
        self.cbxBaudRate.setCurrentIndex(3)
        self.cbxStopBits.addItems(self.Stop)
        self.cbxDataBit.addItems(self.Data)
        self.cbxParity.addItems(self.Check)

    def var_init(self):
        self.com = serial.Serial()
        self.port_name = []
        self.RecvLength = 0
        self.SendLength = 0

        self.csvDataList = []
        self.myPlot = MyPlot()

    # 串口初始化   
    def serial_init(self):
        self.timer_scan = QTimer()
        self.timer_scan.timeout.connect(self.portScan)      # 扫描串口    
        self.timer_scan.start(1000)
        self.portScan()     

    # 信号和槽的连接
    def sig_init(self):                       
        # 显示串口信息
        self.cbxSerialNum.currentTextChanged.connect(self.portInfo)

        # 打开串口
        self.btnOpen.clicked.connect(self.openPort)

        # 接收串口数据
        self.sig_readData.connect(self.slot_readData)

        # 发送数据
        self.btnSend.clicked.connect(self.btn_Send)
        # 定时发送数据
        self.timerSend = QTimer()
        self.timerSend.timeout.connect(self.btn_Send)
        self.chkTimerSend.stateChanged.connect(self.dataSendTimer)

        # 清空发送和接收文本
        self.btnClearSend.clicked.connect(self.sendDataClear)
        self.btnClearRecv.clicked.connect(self.recvDataClear)

        self.tabWidget.currentChanged.connect(self.tabWidgetChanged)

        # 显示数据的更新
        self.dispLable.sig_textUpdata.connect(self.dispLable.slotDispText)

        # 发送和接收计数更新
        self.sig_updateCount.connect(self.updateDataCntDisplay)

        # 打开配置文件
        self.btnConfig.clicked.connect(self.openConfigFile)   

        self.chkHexSend.stateChanged.connect(self.slot_chkHexSend)   

        # 测试Plot
        self.btnPlot.clicked.connect(self.createPlot)   

        # 打开csv文件
        self.pushButton_4.clicked.connect(self.openCSVFile)

    # 打开csv文件
    def openCSVFile(self)  :
        self.fileName, fileType = QFileDialog.getOpenFileName(self, "选取文件", "./", "CSV(*.csv)")
        if self.fileName:
            print(f"file:{self.fileName}")
        # 1.读取csv文件获取数据
        df = pd.read_csv(self.fileName, header=None, index_col=False,)

        # 2.将数据根据配置文件生成数据列表
        for i in range(df.shape[1]-1):
            self.csvDataList.append(df.iloc[:, i].tolist())
        
        # 3.创建三个图表，根据配置文件显示数据



    # 文本显示
    def textDispInit(self):
        # 文本显示的初始化
        self.dispLable = LabelDisp(self.tab_2,config_g.config)
        self.dispLable.createDispLable()

    # 文本显示
    def configTextDispInit(self):
        # 文本显示的初始化
        self.dispConfigLable = ConfigLabelDisp(self.widget,config_g.config)
        self.dispConfigLable.createDispLable() 

    def get_graph_data(self):
        #分两个区域
        pass

    # 图表显示
    def graphDispInit(self):

        self.get_graph_data()
        self.plotWidget = pg.GraphicsLayoutWidget(self.graphWidget)
        self.plotWidget.setGeometry(QRect(25,25,self.tab_3.width(),self.tab_3.height()))

        plot1 = self.plotWidget.addPlot()
        self.x = list(range(300))

        self.data1 = np.random.normal(size=300)
        #self.curve1 = self.plotWidget.plot(self.data1, name="mode1")
        self.curve1 = pg.PlotCurveItem(pen='r')
        plot1.addItem(self.curve1)
        self.ptr1 = 0

        self.data2 = np.random.normal(size=300)
        #self.curve2 = self.plotWidget.plot(self.data1, name="mode2")
        self.curve2 = pg.PlotCurveItem(pen='g')
        plot1.addItem(self.curve2)

        self.graph_timer = QTimer()
        self.graph_timer.timeout.connect(self.updataPlotData)
        self.graph_timer.start(1000)

    # 槽函数：串口发来一帧数据时进行调用更新绘图
    def updataPlotData(self):
        self.data1[:-1] = self.data1[1:]
        self.data1[-1] = np.random.normal()
        #self.data2 = self.data1
        for i in range(len(self.data2)):
            self.data2[i] = self.data1[i] + 10
        # 数据填充到绘制曲线中
        self.curve1.setData(self.data1)
        self.curve2.setData(self.data2)
        # x 轴记录点
        self.ptr1 += 1
        # 重新设定 x 相关的坐标原点
        #self.curve1.setPos(self.ptr1,0)
        #self.curve2.setPos(self.ptr1,0)

    def tabWidgetChanged(self,index):
        print(f"当前的tabWidget index = {index}")
        if index == 0:
            pass
        elif index == 1:     # 实时显示
            pass
        elif index == 2:
            pass

    # 扫描串口
    def portScan(self):
        # 检测所有存在的串口，将信息存储在字典中
        self.Com_Dict = {}
        new_port = []
        port_list = list(serial.tools.list_ports.comports())
        port_list.sort()
 
        for port in port_list:
            self.Com_Dict["%s" % port[0]] = "%s" % port[1]
            new_port.append(port[0])

        if len(self.port_name) != len(new_port):
            self.port_name = new_port
            self.cbxSerialNum.clear()
            self.cbxSerialNum.addItems(self.port_name)

        if len(self.Com_Dict) == 0:
            self.lblSerialInfo.setText("无串口")

        self.portInfo()

    # 显示串口信息
    def portInfo(self):
        info = self.cbxSerialNum.currentText()
        if info != "":
            self.lblSerialInfo.setText(self.Com_Dict[self.cbxSerialNum.currentText()])

    # 打开串口按钮，获取当前UI串口配置信息，发送给串口线程打开串口
    def openPort(self):
        if self.com.is_open:
            self.com.close() 
            config_g.closeFile()   
            print("串口已关闭")
            self.btnOpen.setText("打开串口")
            #self.btnOpen.setStyleSheet("color:red")
            self.btnOpen.setStyleSheet("background-color: rgb(235, 51, 36);")  
            self.timer_scan.stop()                      # 停止扫描串口         
        else:
            try:
                self.com.port = self.cbxSerialNum.currentText()
                self.com.baudrate = int(self.cbxBaudRate.currentText())
                if not self.com.port:
                    raise Exception("please select port")
                self.com.bytesize = int(self.cbxDataBit.currentText())
                self.com.parity = self.cbxParity.currentText()[0]
                self.com.stopbits = float(self.cbxStopBits.currentText())
                self.com.timeout = None
                self.com.open()
                self.btnOpen.setText("关闭串口")
                self.btnOpen.setStyleSheet("background-color: rgb(117, 249, 77);") 
                config_g.createFile()           # 打开串口成功时创建文件保存数据
                print("打开串口成功")
                threading.Thread(target=self.receiveData,daemon=True).start()
            except Exception as e:
                self.com.close()

    # 串口数据接收线程
    def receiveData(self):
        self.stopReceiveData = False
        while (not self.stopReceiveData):
            if self.com.isOpen():
                length = max(1, min(2048, self.com.in_waiting))
                bytes = self.com.read(length)
                if bytes!= None:
                    self.RecvLength += len(bytes)                           # 统计接收字节数
                    self.sig_updateCount.emit()                             # UI更新接收计数
                    self.sig_readData.emit(bytes)                           # 1.向UI线程发送读到的数据，用于txbRecv控件的显示
                    config_g.binFile.write(bytes)                           # 2.将收到的数据存储到bin文件中,原始数据保存
                    oneFrameData = config_g.parseOneFrameData(bytes)        # 3.根据配置文件解析一帧数据，生成一帧解析数据
                    config_g.dataSaveToCSVFile()                            # 4.将一帧解析数据 self.frameEleList 保存到csv文件中
                    if(self.tabWidget.currentIndex() == 1):
                        self.dispLable.sig_textUpdata.emit(oneFrameData)    # 5.将一帧解析数据发送到标签显示控件中，根据配置文件显示数据
                                                                            # 6.将一帧解析数据发送到图表显示控件中，根据配置文件显示数据
            else:
                break
            time.sleep(0.01)

    # 接收从串口接收线程中发来的数据，并在txbRecv控件中显示出来
    def slot_readData(self,data):
        Byte_data = bytes(data)
        if self.chkHexRecv.checkState():
            view_data = ''
            for i in range(0,len(Byte_data)):
                view_data = view_data + '{:02x}'.format(Byte_data[i]) + ' '
            self.txbRecv.insertPlainText(view_data)
        else:
            self.txbRecv.insertPlainText(Byte_data.decode('utf-8','ignore'))
        self.txbRecv.moveCursor(QTextCursor.End)

    def getSendData(self):
        if self.com.is_open:
            send_data = {}
            send_data['data'] = self.txeSend.toPlainText()  
            send_data['Hex'] = self.chkHexSend.checkState()
            send_data['End'] = self.chkEnd.checkState()
            if send_data['Hex'] == 2:
                send_text = send_data['data']
                send_text = send_text.replace(" ", "")
                Byte_data = binascii.unhexlify(send_text)     # 使用unhexlify放法将16进制字符串转换为字节串
                if send_data['End'] == 2:
                    Byte_data = Byte_data + b'\r\n'
                    return  Byte_data
                else:            
                    return  Byte_data
            else:
                if send_data['End'] == 2:
                    send_buff = send_data['data']  + "\r\n"
                else:            
                    send_buff = send_data['data']
                Byte_data = str.encode(send_buff)
                return  Byte_data

    # 发送按钮点击，txeSend控件中的数据发送到串口发送线程
    def btn_Send(self):
        if self.com.is_open:
            Byte_data = self.getSendData()
            self.com.write(Byte_data) 
            self.SendLength += len(Byte_data)
            self.sig_updateCount.emit()

    # 勾选定时发送数据，如果打开了串口启动定时发送定时器
    def dataSendTimer(self):
        if self.chkTimerSend.isChecked():
            try:
                self.timerSend.start(int(self.ldtSendInterval.text()))
            except ValueError:
                QMessageBox.critical(self, 'Wrong Data', '请输入定时时间!')
                return None
            self.ldtSendInterval.setEnabled(False)
        else:
            self.timerSend.stop()
            self.ldtSendInterval.setEnabled(True)

    # 更新发送和接收计数
    def updateDataCntDisplay(self):
        self.lblSend.setText("发送：" + str(self.SendLength))
        self.lblRecv.setText("接收：" + str(self.RecvLength))

    # 16进制发送设置
    def slot_chkHexSend(self,state):
        if state == 2:
            send_text = self.txeSend.toPlainText()
            Byte_text = str.encode(send_text)
            view_data = ''
            for i in range(0,len(Byte_text)):
                view_data = view_data + '{:02x}'.format(Byte_text[i]) + ' '
            self.txeSend.setText(view_data)
        else:
            send_list = []
            send_text = self.txeSend.toPlainText()
            while send_text != '':
                try:
                    num = int(send_text[0:2],16)
                except:
                    QMessageBox.warning(self,'错误信息','请正确输入16进制数据')
                    return 
                send_text = send_text[2:].strip()
                send_list.append(num)
            input_s = bytes(send_list)
            self.txeSend.setText(input_s.decode())

    # 清除发送数据文本
    def sendDataClear(self):
        self.txeSend.clear()
        self.SendLength = 0
        self.RecvLength = 0
        self.lblSend.setText("发送：")
        self.lblRecv.setText("接收：")

    # 清除接收数据文本
    def recvDataClear(self):
        self.txbRecv.clear()
        self.SendLength = 0
        self.RecvLength = 0
        self.lblSend.setText("发送：")
        self.lblRecv.setText("接收：")

    # 状态栏添加发送和接受数据量的显示部件
    def addStateBarView(self):                          
        self.lblRecv = QLabel()
        self.lblRecv.setMinimumSize(QSize(130, 0))
        self.lblRecv.setText("接收：")
        self.statusbar.addWidget(self.lblRecv) 
   
        self.lblSend = QLabel()
        self.lblSend.setMinimumSize(QSize(130, 0))
        self.lblSend.setText("发送：")
        self.statusbar.addWidget(self.lblSend) 


    def openConfigFile(self):
        '''
            1.创建一个对话框，读取配置文件通过编辑控件显示出来
            2.保存配置文件
        '''
        pass

    def createPlot(self):
        '''
            1.创建绘图窗口
            2.将数据发送到绘图窗口
        '''
        self.myPlot.show()

    def resizeEvent(self, a0: QResizeEvent) -> None:
        self.dispLable.lableDispLayout()
        self.dispConfigLable.lableDispLayout()
        self.plotWidget.setGeometry(QRect(25,25,self.tab_3.width()-25,self.tab_3.height()-25))
        print("tabWidget.width():",self.tabWidget.width(),",tabWidget.height():",self.tabWidget.height())
        print("tab_2.width():",self.tab_2.width(),",tab_2.height():",self.tab_2.height())
        return super().resizeEvent(a0)

    def closeEvent(self, event):
        """程序关闭的处理"""
        print("关闭程序")
        config_g.closeFile()                        # 关闭打开的文件        
        self.com.close()                            # 关闭串口
        self.stopReceiveData = True                 # 关闭数据接收线程
        '''
            if not self.binFile.closed:
                self.binFile.close()
            if not self.csvFile.closed:
                self.csvFile.close() 
        '''
        return super().closeEvent(event)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWindow = MyMainWindow()
    
    
    
    myWindow.show()

    sys.exit(app.exec_())



